#include <climits>
#include <fstream>

#include "model/object.h"
#include <vector>
#include <QDebug>

Object::Object(): Limits() {
    this->visible = true;
}
Object::Object(const std::string &filename): Object() {

    std::ifstream f (filename, std::ios::binary | std::ios::in);

    if (!f.is_open()) {
        throw Object::InvalidFile;
    }

    unsigned int points_s, faces_s, fpoints_s, id;

    f >> points_s;
    f >> faces_s;

    std::vector <Vertex> points(points_s);
    for (std::size_t i = 0; i < points_s; i++) {
        coord x, y, z;
        f >> x;
        f >> y;
        f >> z;
        points[i] = Vertex(x, y, z);              // adiciona a lista de pontos
    }

    this->resetLimits();
    for (std::size_t i = 0; i < faces_s; i++) {                 // para cada face
        f >> fpoints_s;
        std::vector <Vertex> fpoints;
        for (std::size_t j = 0; j < fpoints_s; j++) {
            f >> id;
            fpoints.push_back(points[id]);           // adiciona os pontos a uma lista temporaria
        }
        this->add(fpoints);
    }

    f.close();
}

Object::Object(const Object& object) {
    this->visible = object.visible;
    unsigned int i;

    i = 0;
    vertexs.resize(object.vertexs.size(), nullptr);
    for ( const Vertex* vertex: object.vertexs ) {
        Vertex* newvertex = new Vertex(vertex->x, vertex->y, vertex->z);
        newvertex->index = i;
        vertexs[i++] = newvertex;
    }

    i = 0;
    edges.resize(object.edges.size(), nullptr);
    for ( const HalfEdge* edge: object.edges ) {
        HalfEdge *newedge = new HalfEdge(*vertexs[edge->begin->index], *vertexs[edge->end->index]);
        newedge->index = i;
        newedge->begin->edge = newedge;
        edges[i++] = newedge;
    }

    i = 0;
    faces.resize(object.faces.size(), nullptr);
    for ( const Face *face: object.faces ) {
        Face *newface = new Face(*this, *edges[face->edge->index]);
        newface->index = i;
        faces[i++] = newface;
    }

    i = 0;
    for ( const HalfEdge* edge: object.edges ) {
        HalfEdge *newedge = edges[i++];
        newedge->previous = edges[edge->previous->index];
        newedge->next = edges[edge->next->index];

        if ( edge->complement )
            newedge->complement = edges[edge->complement->index];

        newedge->face = faces[edge->face->index];
    }

    greater = Vertex(object.greater);
    lower = Vertex(object.lower);

    properties = object.properties;
}

Object::~Object() {
    for ( Vertex * vertex: vertexs )
        delete vertex;
    vertexs.clear();

    for ( HalfEdge * edge: edges )
        delete edge;
    edges.clear();

    for ( Face * face: faces )
        delete face;
    faces.clear();
}

// TODO Refatorar este método
Face* Object::add(const std::vector<Vertex> &points) {

    std::size_t points_s = points.size(),
            edge_i;

    if ( points_s < 3 )
        throw Object::InsuficentPoints;

    Face *r;
    std::vector<Vertex *> p = this->makeVertex(points);
    std::vector<HalfEdge *> e = Vertex::join(p);

    r = new Face(*this, *e[0]);
    r->index = this->faces.size();
    this->faces.push_back(r);

    std::vector<HalfEdge*>::iterator iterator, last;
    HalfEdge *edge;

    edge_i = this->edges.size();
    for(std::size_t i=0; i<points_s; i++) {
        edge = e[i];
        edge->index = edge_i++;
        this->edges.push_back(edge);

        edge->face = r;
        iterator = this->edges.begin();
        last = this->edges.end();

        for (; iterator<last; iterator++) {
            if ( (*iterator)->begin == edge->end && (*iterator)->end == edge->begin ) {
                edge->complement = (*iterator);
                (*iterator)->complement = edge;
                break;
            }
        }
    }

    return r;
}

Vertex *Object::makeVertex(const Vertex &vertex) {
    for ( Vertex* current: this->vertexs ) {
        if ( *current == vertex )
            return current;
    }

    Vertex* v = new Vertex(vertex);
    v->index = this->vertexs.size();
    this->vertexs.push_back(v);
    this->updateLimits(*v);

    return v;
}

std::vector<Vertex*> Object::makeVertex(const std::vector<Vertex> &vertexs) {
    std::vector<Vertex*> r;

    for (const Vertex& v: vertexs) {
        r.push_back(this->makeVertex(v));
    }

    return r;
}

const std::vector<const Face*> &Object::getFaces() const {
    return *reinterpret_cast<const std::vector<const Face*>*>(&this->faces);
}

const std::vector<const HalfEdge*> &Object::getEdges() const {
    return *reinterpret_cast<const std::vector<const HalfEdge*>*>(&this->edges);
}

const std::vector<const Vertex *> &Object::getVertex() const {
    return *reinterpret_cast<const std::vector<const Vertex*>*>(&this->vertexs);
}

const std::vector<Vertex *> &Object::getVertex() {
    return this->vertexs;
}

void Object::apply(const MatrixTransformation &matrix) {
    coord x, y, z, w;
    const coord (*m)[4] = matrix.data;

    this->resetLimits();

    for ( Vertex* p: vertexs ) {
        x = p->x;
        y = p->y;
        z = p->z;
        w = p->w;

        p->x = (m[0][0] * x) + (m[0][1] * y) + (m[0][2] * z) + (m[0][3] * w);
        p->y = (m[1][0] * x) + (m[1][1] * y) + (m[1][2] * z) + (m[1][3] * w);
        p->z = (m[2][0] * x) + (m[2][1] * y) + (m[2][2] * z) + (m[2][3] * w);
        p->w = (m[3][0] * x) + (m[3][1] * y) + (m[3][2] * z) + (m[3][3] * w);

        this->updateLimits(*p);
    }
}

void Object::save(const std::string &fileName) const {

    int points_s = this->vertexs.size();
    int faces_s = this->faces.size();

    std::fstream fstream;
    fstream.open(fileName, std::ios::out);

    fstream << points_s << " " << faces_s << "\n";

    for (const Vertex *vertex: this->vertexs)
        fstream << vertex->x << " " << vertex->y << " " << vertex->z << std::endl;

    for (const Face *face: this->faces) {
        const std::vector <const Vertex*> &fpoints = face->getVertexs();

        points_s = fpoints.size();
        fstream << points_s << " ";

        for (const Vertex *vertex: fpoints)
            fstream << vertex->index << " ";

        fstream << std::endl;
    }

    fstream.close();
}

const Lighting &Object::getLightProperties() const{
    return this->properties;
}

void Object::setLightProperties(const Lighting &properties){
    this->properties = properties;
}

void Object::setVisible(bool b) {
    this->visible = b;
}

bool Object::isVisible() const {
    return this->visible;
}

light_t Object::getKar() {
    return this->properties.kar;
}

light_t Object::getKag() {
    return this->properties.kag;
}
light_t Object::getKab() {
    return this->properties.kab;
}

light_t Object::getKdr() {
    return this->properties.kdr;
}

light_t Object::getKdg() {
    return this->properties.kdg;
}

light_t Object::getKdb() {
    return this->properties.kdb;
}

light_t Object::getKsr() {
    return this->properties.ksr;
}

light_t Object::getKsg() {
    return this->properties.ksg;
}

light_t Object::getKsb() {
    return this->properties.ksb;
}

light_t Object::getN() {
    return this->properties.n;
}

void Object::setKar(light_t k) {
    this->properties.kar = k;
}

void Object::setKag(light_t k) {
    this->properties.kag = k;
}
void Object::setKab(light_t k) {
    this->properties.kab = k;
}

void Object::setKsr(light_t k) {
    this->properties.ksr = k;
}

void Object::setKsg(light_t k) {
    this->properties.ksg = k;
}
void Object::setKsb(light_t k) {
    this->properties.ksb = k;
}

void Object::setKdr(light_t k) {
    this->properties.kdr = k;
}

void Object::setKdg(light_t k) {
    this->properties.kdg = k;
}

void Object::setKdb(light_t k) {
    this->properties.kdb = k;
}

void Object::setN(light_t n) {
    this->properties.n = n;
}



